﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using COFE.Core;
using COFE.Core.Lister;
using COFE.Core.Validater;
using System.IO;
using COFE.Core.Utils;
using COFE.Core.Entry;

namespace COFE.Data.Profile
{
    public class DBEntryPropertyManager : IDBEntryPropertyManager
    {
        #region Constructor


        #endregion

        #region Methods


        #region private methods
        private bool isDatabaseProperty(object property)
        {
            return property is DatabaseProperties;
        }

        private bool isFileBasedFSEntryProperty(COFEDB2 context, Entry dbEntry, object property)
        {
            if (dbEntry is FileEntry && dbEntry.PrimaryVolumeId != dbEntry.SecondaryVolumeId)
                switch ((int)property)
                {
                    case (int)DatabaseProperties.LastListTime:
                    case (int)DatabaseProperties.PropertyRefreshTime:
                    case (int)DirectoryInfoProperties.HasFiles:
                    case (int)DirectoryInfoProperties.HasSubFolders:
                    case (int)DirectoryInfoProperties.FileCount:
                    case (int)DirectoryInfoProperties.SubFolderCount:
                        return true;
                }

            return false;
        }


        public static bool IsEntryProperty(object property)
        {
            return DBEntryHelper.EntryPropertyGetterDic.ContainsKey(property) ||
                DBEntryHelper.FileEntryPropertyGetterDic.ContainsKey(property) ||
                DBEntryHelper.FolderEntryPropertyGetterDic.ContainsKey(property);
        }

        private bool isEntryProperty(COFEDB2 context, Entry dbEntry, object property)
        {
            return DBEntryHelper.EntryPropertyGetterDic.ContainsKey(property) ||
                (dbEntry is FileEntry && DBEntryHelper.FileEntryPropertyGetterDic.ContainsKey(property)) ||
                (dbEntry is FolderEntry && DBEntryHelper.FolderEntryPropertyGetterDic.ContainsKey(property));
        }

        private object getProperty<T, EPType>(COFEDB2 context, Entry dbEntry, object property,
            Func<EPType, T> getValueFunc) where EPType : EntryProperty
        {
            if (!dbEntry.EntryProperties.IsLoaded)
            {
                COFEDBShared.DBOperations.ReadDatabase(context,
                    (checkedContext) =>
                    {
                        checkedContext.Attach(dbEntry);
                        dbEntry.EntryProperties.Load();
                        checkedContext.Detach(dbEntry);
                    });
            }

            var supProps = (EPType)dbEntry.EntryProperties.FirstOrDefault(p => p.PropertyId == (int)property);
            if (supProps != null)
                return getValueFunc(supProps);

            if (PropertyTypeExtension.HasDefaultValue(property))
                return PropertyTypeExtension.GetDefaultValue(property);

            throw new NotImplementedException("Property not cached.");
        }

        private void updateProperty<T, EPType>(COFEDB2 context, Entry dbEntry, object property, T value, Func<EPType, T> getValueFunc,
           Action<EPType, T> setValueFunc, Func<Entry, object, T, EPType> constructEPTypeFunc) where EPType : EntryProperty
        {
            var supProps = (EPType)dbEntry.EntryProperties.FirstOrDefault(p => p.PropertyId == (int)property);

            if (supProps != null)
            {
                T currentValue = getValueFunc(supProps);
                if (!currentValue.Equals(value))
                    setValueFunc(supProps, value);
            }
            else
                context.EntryProperties.AddObject(constructEPTypeFunc(dbEntry, property, value));


        }

        private void updateProperty<T, EPType>(COFEDB2 context, Entry dbEntry, PropertyPair ppair, Func<EPType, T> getValueFunc,
           Action<EPType, T> setValueFunc, Func<Entry, object, T, EPType> constructEPTypeFunc) where EPType : EntryProperty
        {
            updateProperty(context, dbEntry, ppair.Property, (T)ppair.Value, getValueFunc, setValueFunc, constructEPTypeFunc);
        }


        private static Func<Entry, object, Int32, EntryProperty_Int32> constructInt32Func =
             (e, p, v) => EntryProperty_Int32.CreateEntryProperty_Int32(e.EntryId, (int)p, v);
        private static Func<Entry, object, bool, EntryProperty_Bool> constructBoolFunc =
             (e, p, v) => EntryProperty_Bool.CreateEntryProperty_Bool(e.EntryId, (int)p, v);
        private static Func<Entry, object, String, EntryProperty_String> constructStringFunc =
             (e, p, v) => EntryProperty_String.CreateEntryProperty_String(e.EntryId, (int)p, v);
        private static Func<Entry, object, DateTime, EntryProperty_DateTime> constructDateTimeFunc =
             (e, p, v) => EntryProperty_DateTime.CreateEntryProperty_DateTime(e.EntryId, (int)p, v);
        private static Func<Entry, object, Guid, EntryProperty_Guid> constructGuidFunc =
             (e, p, v) => EntryProperty_Guid.CreateEntryProperty_Guid(e.EntryId, (int)p, v);
        private static Func<Entry, object, Single, EntryProperty_Single> constructSingleFunc =
         (e, p, v) => EntryProperty_Single.CreateEntryProperty_Single(e.EntryId, (int)p, v);

        private void cacheProperty(COFEDB2 context, Entry dbEntry, PropertyPair ppair)
        {
            if (ppair.Type == typeof(Int32))
                updateProperty<Int32, EntryProperty_Int32>(context, dbEntry, ppair, (ep) => ep.Value,
                    (ep, v) => ep.Value = v, constructInt32Func);
            if (ppair.Type == typeof(Boolean))
                updateProperty<Boolean, EntryProperty_Bool>(context, dbEntry, ppair, (ep) => ep.Value,
                    (ep, v) => ep.Value = v, constructBoolFunc);
            if (ppair.Type == typeof(String))
                updateProperty<String, EntryProperty_String>(context, dbEntry, ppair, (ep) => ep.Value,
                    (ep, v) => ep.Value = v, constructStringFunc);
            if (ppair.Type == typeof(DateTime))
                updateProperty<DateTime, EntryProperty_DateTime>(context, dbEntry, ppair, (ep) => ep.Value,
                    (ep, v) => ep.Value = v, constructDateTimeFunc);
            if (ppair.Type == typeof(Guid))
                updateProperty<Guid, EntryProperty_Guid>(context, dbEntry, ppair, (ep) => ep.Value,
                    (ep, v) => ep.Value = v, constructGuidFunc);
            if (ppair.Type == typeof(Single))
                updateProperty<Single, EntryProperty_Single>(context, dbEntry, ppair, (ep) => ep.Value,
                    (ep, v) => ep.Value = v, constructSingleFunc);
        }


        public void setEntryProperty(COFEDB2 context, Entry dbEntry, object property, object value)
        {
            if (DBEntryHelper.EntryPropertySetterDic.ContainsKey(property))
                DBEntryHelper.EntryPropertySetterDic[property](dbEntry, value);
            else if (dbEntry is FileEntry && DBEntryHelper.FileEntryPropertySetterDic.ContainsKey(property))
                DBEntryHelper.FileEntryPropertySetterDic[property](dbEntry as FileEntry, value);
            else if (dbEntry is FolderEntry && DBEntryHelper.FolderEntryPropertySetterDic.ContainsKey(property))
                DBEntryHelper.FolderEntryPropertySetterDic[property](dbEntry as FolderEntry, value);
            else throw new KeyNotFoundException(property.ToString());

            context.SaveChanges();
        }
        public object getEntryProperty(COFEDB2 context, Entry dbEntry, object property)
        {
            if (DBEntryHelper.EntryPropertyGetterDic.ContainsKey(property))
                return DBEntryHelper.EntryPropertyGetterDic[property](dbEntry);
            if (dbEntry is FileEntry && DBEntryHelper.FileEntryPropertyGetterDic.ContainsKey(property))
                return DBEntryHelper.FileEntryPropertyGetterDic[property](dbEntry as FileEntry);
            if (dbEntry is FolderEntry && DBEntryHelper.FolderEntryPropertyGetterDic.ContainsKey(property))
                return DBEntryHelper.FolderEntryPropertyGetterDic[property](dbEntry as FolderEntry);

            throw new KeyNotFoundException(property.ToString());
        }
        private void setProperty(COFEDB2 context, Entry dbEntry, object property, object value)
        {
            dbEntry.EntryProperties.Load();
            cacheProperty(context, dbEntry, new PropertyPair(property, value));
            context.SaveChanges();
        }
        private object getProperty(COFEDB2 context, Entry dbEntry, object property)
        {
            if (!(dbEntry.EntryProperties.Any(ep => ep.PropertyId == (int)property)))
            {
                //Need load entry properties.
                dbEntry = COFEDBShared.DBEntryManager.LookupEntry(context, dbEntry.EntryId);
                dbEntry.EntryProperties.Load();
            }

            var type = PropertyTypeExtension.GetDefaultPropertyType(property);
            if (type == typeof(Int32)) return getProperty<Int32, EntryProperty_Int32>(context, dbEntry, property, (ep) => ep.Value);
            if (type == typeof(Boolean)) return getProperty<Boolean, EntryProperty_Bool>(context, dbEntry, property, (ep) => ep.Value);
            if (type == typeof(String)) return getProperty<String, EntryProperty_String>(context, dbEntry, property, (ep) => ep.Value);
            if (type == typeof(DateTime)) return getProperty<DateTime, EntryProperty_DateTime>(context, dbEntry, property, (ep) => ep.Value);
            if (type == typeof(Guid)) return getProperty<Guid, EntryProperty_Guid>(context, dbEntry, property, (ep) => ep.Value);
            if (type == typeof(Single)) return getProperty<Single, EntryProperty_Single>(context, dbEntry, property, (ep) => ep.Value);

            throw new NotSupportedException("Property not supported.");
        }
        #endregion

        public void SetProperty(COFEDB2 context, Entry dbEntry, object property, object value)
        {
            if (isEntryProperty(context, dbEntry, property))
                setEntryProperty(context, dbEntry, property, value);
            else setProperty(context, dbEntry, property, value);
        }

        public object[] GetCachedProperties(COFEDB2 context, Entry dbEntry)
        {
            List<object> retList = new List<object>();
            foreach (var ep in dbEntry.EntryProperties)
            {
                try
                {
                    object property = COFEShared.PropertyDictionary.GetProperty(ep.PropertyId);
                    retList.Add(property);
                }
                catch (KeyNotFoundException) { }
            }

            retList.AddRange(DBEntryHelper.EntryPropertyGetterDic.Keys);
            if (dbEntry is FileEntry)
            {
                retList.AddRange(DBEntryHelper.FileEntryPropertyGetterDic.Keys);
                if (((COFEAttributes)dbEntry.COFEAttributes).HasFlag(COFEAttributes.FileBasedFS))  //File Based FS Entry
                //dbEntry.PrimaryVolumeId != dbEntry.SecondaryVolumeId) //Converted Entry
                {
                    retList.Add(DatabaseProperties.LastListTime);
                    retList.Add(DirectoryInfoProperties.HasSubFolders);
                    retList.Add(DirectoryInfoProperties.HasFiles);
                    retList.Add(DirectoryInfoProperties.SubFolderCount);
                    retList.Add(DirectoryInfoProperties.FileCount);
                }
            }
            if (dbEntry is FolderEntry)
                retList.AddRange(DBEntryHelper.FolderEntryPropertyGetterDic.Keys);

            return retList.ToArray();

        }

        public object GetProperty(COFEDB2 context, Entry dbEntry, object property)
        {
            if (isEntryProperty(context, dbEntry, property))
                return getEntryProperty(context, dbEntry, property);
            else return getProperty(context, dbEntry, property);
        }

        public bool IsPropertyCachable(object property)
        {
            Type propertyType = PropertyTypeExtension.GetDefaultPropertyType(property);
            return (propertyType == typeof(Int32)
                || propertyType == typeof(Boolean)
                || propertyType == typeof(String)
                || propertyType == typeof(DateTime)
                || propertyType == typeof(Guid))
                || propertyType == typeof(Single)
            ;
        }

        public void CachePrimaryProperties(COFEDB2 context, Entry dbEntry, IPropertyProvider propProvider)
        {
            dbEntry.EntryProperties.Load();
            foreach (var prop in propProvider.GetSupportedProperty())
                if (PrimaryPropertyExtension.IsPrimaryProperty(prop) &&
                    !DBEntryPropertyManager.IsEntryProperty(prop) &&
                    IsPropertyCachable(prop))
                    cacheProperty(context, dbEntry, propProvider.GetProperty(prop));

            if (dbEntry is FileEntry && (dbEntry as FileEntry).IsFileBasedFS)
                if (!dbEntry.EntryProperties.Any(ep => ep.PropertyId == (int)DatabaseProperties.LastListTime))
                    cacheProperty(context, dbEntry, new PropertyPair(DatabaseProperties.LastListTime, COFEDBShared.DTMin));
        }

        public void CacheProperties(Guid dbEntryId, IInternalFileSystemInfoExA entry)
        {
            var currentUserProfile = COFEShared.CurrentUserProfile;

            COFEShared.UserProfileManager.SetCurrentUserProfile(currentUserProfile);
            List<object> cachedProperties = new List<object>();

            COFEDBShared.DBOperations.UpdateDatabase(
                (context) => COFEDBShared.DBEntryManager.LookupEntry(context, dbEntryId),
                (context, dbEntry) =>
                {
                    if (dbEntry != null)
                    {
                        if (DateTime.Now.Subtract(dbEntry.PropertyRefreshTime) > COFEDBShared.TTLPropertyValues)
                        {
                            dbEntry.PropertyRefreshTime = DateTime.UtcNow;
                            dbEntry.EntryProperties.Load();
                            //context.SaveChanges();

                            foreach (var provider in COFEShared.SecondaryPropertyProviderManager.GetSecondaryPropertyProviders<IFileSystemInfo>())
                                foreach (var prop in provider.GetSupportedProperty(entry))
                                    if (IsPropertyCachable(prop) && !cachedProperties.Contains(prop))
                                    //If cache all property all the property is primary property.                                        
                                    {
                                        //Console.Write(((int)prop).ToString() + ",");
                                        cachedProperties.Add(prop);
                                        if (prop.Equals(FileInfoProperties.CRC) && dbEntry is FileEntry)
                                        {
                                            (dbEntry as FileEntry).CRC = provider.GetProperty(entry, prop).ValueAsString;
                                        }
                                        cacheProperty(context, dbEntry, provider.GetProperty(entry, prop));
                                        //context.SaveChanges();
                                    }

                            context.SaveChanges();
                        }
                    }
                });




        }

        #endregion

        #region Data




        #endregion

        #region Public Properties



        #endregion



    }
}
